namespace NTrace
{
  using System;
  using System.Collections.Generic;
  using ICSharpCode.NRefactory.Ast;

  public class Environment
  {
    internal const String TraceFlagsPropertyName = "Flags";
    internal const String TraceLevelPropertyName = "Level";
    internal const String TraceEnabledPropertyName = "TraceEnabled";
    internal static readonly PrimitiveExpression zeroExpression = new PrimitiveExpression(0, null);

    /// <summary>
    /// The tool configuration
    /// </summary>
    private Configuration configuration;

    /// <summary>
    /// The directory into which all preprocessed files and TMF files will be written.
    /// </summary>
    private String outputFolder;

    /// <summary>
    /// This is the internally-accessible placeholder variable that we hang the tracing stuff off of (required).
    /// </summary>
    private String etwProviderVariable;

    /// <summary>
    /// This is the GUID we'll use for all tracing coming from this assembly (required).
    /// </summary>
    private Guid providerId;

    /// <summary>
    /// A "friendly name" for the provider (required).
    /// </summary>
    private String friendlyName;

    /// <summary>
    /// If function entry/exit should be traced, the name of the flag to use for function tracing.
    /// </summary>
    private int functionTraceFlag;

    /// <summary>
    /// If function entry/exit should be traced, the name of the trace level (default: StackTracing).
    /// </summary>
    private int functionTraceLevel;

    /// <summary>
    /// The fake name we're using for our tracing class.
    /// </summary>
    private String traceClassName = "EtwTrace";

    /// <summary>
    /// The fake name we're using for our trace levels enum.
    /// </summary>
    private String traceLevelName = "EtwTraceLevel";

    /// <summary>
    /// The fake name we're using for our trace flags enum.
    /// </summary>
    private String traceFlagName = "EtwTraceFlag";

    /// <summary>
    /// The flavor of build we're generating.
    /// </summary>
    private String buildFlavor;

    public Environment() : this(Configuration.Load())
    {
    }

    public Environment(String configurationFile) : this(Configuration.Load(configurationFile))
    {
    }

    private Environment(Configuration configuration)
    {
      this.configuration = configuration;
    }

    /// <summary>
    /// The tool configuration
    /// </summary>
    /// <value>A <see cref="Configuration"/> containing the tool's configuration.</value>
    public Configuration Configuration
    {
      get
      {
        return this.configuration;
      }
    }

    /// <summary>
    /// This is the internally-accessible placeholder variable that we hang the tracing stuff off of (required).
    /// </summary>
    public String EtwProviderVariable
    {
      get
      {
        return this.etwProviderVariable;
      }
      set
      {
        this.etwProviderVariable = value;
      }
    }

    /// <summary>
    /// This is the GUID we'll use for all tracing coming from this assembly (required).
    /// </summary>
    public Guid ProviderId
    {
      get
      {
        return this.providerId;
      }
      set
      {
        this.providerId = value;
      }
    }

    /// <summary>
    /// A "friendly name" for the provider (required).
    /// </summary>
    public String FriendlyName
    {
      get
      {
        return this.friendlyName;
      }
      set
      {
        this.friendlyName = value;
      }
    }

    /// <summary>
    /// If function entry/exit should be traced, the name of the flag to use for function tracing.
    /// </summary>
    public int FunctionTraceFlag
    {
      get
      {
        return this.functionTraceFlag;
      }
      set
      {
        this.functionTraceFlag = value;
      }
    }

    /// <summary>
    /// If function entry/exit should be traced, the name of the trace level (default: StackTracing).
    /// </summary>
    public int FunctionTraceLevel
    {
      get
      {
        return this.functionTraceLevel;
      }
      set
      {
        this.functionTraceLevel = value;
      }
    }

    /// <summary>
    /// The fake name we're using for our tracing class.
    /// </summary>
    public String TraceClassName
    {
      get
      {
        return this.traceClassName;
      }
      set
      {
        this.traceClassName = value;
      }
    }

    /// <summary>
    /// The fake name we're using for our trace levels enum.
    /// </summary>
    public String TraceLevelName
    {
      get
      {
        return this.traceLevelName;
      }
      set
      {
        this.traceLevelName = value;
      }
    }

    /// <summary>
    /// The fake name we're using for our trace flags enum.
    /// </summary>
    public String TraceFlagName
    {
      get
      {
        return this.traceFlagName;
      }
      set
      {
        this.traceFlagName = value;
      }
    }

    /// <summary>
    /// Gets the flavor of build we're generating.
    /// </summary>
    public String BuildFlavor
    {
      get { return this.buildFlavor; }
      set { this.buildFlavor = value; }
    }

    /// <summary>
    /// The directory into which all preprocessed files and TMF files will be written.
    /// </summary>
    public String OutputFolder
    {
      get { return outputFolder; }
      set { outputFolder = value; }
    }

    internal Expression GetTraceEnabledCheck()
    {
      // Get a reference to the Enabled property
      MemberReferenceExpression output = new MemberReferenceExpression(this.GetTraceProviderReference(), "Enabled");
      return output;
    }

    internal Expression GetTraceFlagLevelCheck(int flag, int level)
    {
      Expression output;
      
      Expression flagCheck = this.GetPropertyMaskCheck(Environment.TraceFlagsPropertyName, flag);
      if (level > 0)
      {
        Expression levelCheck = this.GetPropertyLevelCheck(Environment.TraceLevelPropertyName, level);

        output = new BinaryOperatorExpression(
            new ParenthesizedExpression(flagCheck), 
            BinaryOperatorType.LogicalAnd, 
            new ParenthesizedExpression(levelCheck));
      }
      else
      {
        output = new ParenthesizedExpression(flagCheck);
      }

      return output;
    }

    internal Expression GetTraceFlagCheck(int flag)
    {
      Expression output = this.GetPropertyMaskCheck(Environment.TraceFlagsPropertyName, flag);
      return output;
    }

    internal Expression GetTraceLevelCheck(int level)
    {
      Expression output = this.GetPropertyLevelCheck(Environment.TraceLevelPropertyName, level);
      return output;
    }

    private Expression GetPropertyMaskCheck(String propertyName, int value)
    {
      // Get a reference to the property
      MemberReferenceExpression propertyReference = new MemberReferenceExpression(this.GetTraceProviderReference(), propertyName);

      CastExpression castExpression = new CastExpression(new TypeReference("int", true), propertyReference, CastType.Cast);

      // Wrap the value we're comparing against
      PrimitiveExpression valueExpression = new PrimitiveExpression(value, null);

      // Perform a binary mask check
      BinaryOperatorExpression maskExpression = new BinaryOperatorExpression(castExpression, BinaryOperatorType.BitwiseAnd, valueExpression);
      
      BinaryOperatorExpression check = new BinaryOperatorExpression(new ParenthesizedExpression(maskExpression), BinaryOperatorType.InEquality, Environment.zeroExpression);

      return check;
    }

    private Expression GetPropertyLevelCheck(String propertyName, int value)
    {
      // Get a reference to the property
      MemberReferenceExpression propertyReference = new MemberReferenceExpression(this.GetTraceProviderReference(), propertyName);

      CastExpression castExpression = new CastExpression(new TypeReference("int", true), propertyReference, CastType.Cast);

      // Wrap the value we're comparing against
      PrimitiveExpression valueExpression = new PrimitiveExpression(value, null);

      // Ensure the runtime value is greater than or equal to the threshold supplied
      BinaryOperatorExpression levelExpression = new BinaryOperatorExpression(castExpression, BinaryOperatorType.GreaterThanOrEqual, valueExpression);

      return levelExpression;
    }

    internal ExpressionStatement GetFunctionTraceExpression(String traceMessage)
    {
      List<Expression> arguments = new List<Expression>();
      arguments.Add(new PrimitiveExpression(traceMessage, null));

      // Personally, I consider this to be a confusing API - at the very least
      // we should have a NOP MethodReferenceExpression that simply wraps
      // MemberReferenceExpression
      MemberReferenceExpression methodCall = new MemberReferenceExpression(
        this.GetTraceProviderReference(),
        "Trace");
      InvocationExpression invocationExpression = new InvocationExpression(
        methodCall,
        arguments);
      ExpressionStatement output = new ExpressionStatement(invocationExpression);

      return output;
    }

    internal Expression GetTraceProviderReference()
    {
      String providerVariable = this.EtwProviderVariable;
      System.Diagnostics.Debug.Assert(providerVariable != null, "Expected a non-null provider instance value.");

      Int32 delimiterIndex = providerVariable.LastIndexOf(".");
      System.Diagnostics.Debug.Assert(delimiterIndex < providerVariable.Length);
      String className = providerVariable.Substring(0, delimiterIndex);
      String fieldName = providerVariable.Substring(delimiterIndex + 1);

      Expression hostingTypeReference = new TypeReferenceExpression(new TypeReference(className));
      Expression output = new MemberReferenceExpression(hostingTypeReference, fieldName);
      return output;
    }
  }
}
